'''
Created on Sept 16, 2013

@author: Patrick Janssen
'''

import sys, os
from string import Template
from core.modules.vistrails_module import Module, ModuleError
import csv
import string
import datetime
import json
from constants import *

''' ---------------------------------------------------------------------------
Helper functions for writing rad files
--------------------------------------------------------------------------- '''

def surface(name, material, points):
    surface = material + " polygon " + name + "\n"+\
    "0\n"+\
    "0\n"+\
    str(len(points) * 3) + "\n"
    for point in points:
        surface = surface + "    " + str(point[0]) + "  " + str(point[1]) + "  " + str(point[2]) + "\n"
    surface = surface + "\n"
    return surface

def brightfunc(cal_file_name):
    brightfunc = "void brightfunc skyfunc\n"+\
    "2 skybright " + cal_file_name + "\n"+\
    "0\n"+\
    "0\n"
    brightfunc = brightfunc + "\n"
    return brightfunc 

def glow(name, colour):
    glow = "skyfunc glow " + name + "\n"+\
    "0\n"+\
    "0\n"+\
    "4 " +\
    str(colour[0])+ " "  +\
    str(colour[1]) + " " +\
    str(colour[2]) + " 0\n"
    glow = glow + "\n"
    return glow 

def source(name, material, direction):
    source = material + " source " + name + "\n"+\
    "0\n"+\
    "0\n"+\
    "4 " +\
    str(direction[0])+ " "  +\
    str(direction[1]) + " " +\
    str(direction[2]) + " 180\n"
    source = source + "\n"
    return source

def material_glass(name, transmission):
    material_glass = "# Glass material\n"+\
    "void glass " + name + "\n"+\
    "0\n"+\
    "0\n"+\
    "3 " +\
    str(transmission[0])+ " "  +\
    str(transmission[1]) + " " +\
    str(transmission[2]) + "\n\n"
    return material_glass


def material_plastic(name, colour, spec, rough):
    material_plastic = "# Plastic material\n"+\
    "void plastic " + name + "\n"+\
    "0\n"+\
    "0\n"+\
    "5 " +\
    str(colour[0])+ " "  +\
    str(colour[1]) + " " +\
    str(colour[2]) + " " +\
    str(spec) + " " +\
    str(rough) + "\n\n"
    return material_plastic

def sensor(position, normal):
    if not position or not normal:
        raise Exception
    sensor = str(position[0]) + " " + str(position[1]) + " " + str(position[2]) + " " + str(normal[0]) + " " + str(normal[1]) + " " + str(normal[2]) + "\n"
    return sensor

''' ---------------------------------------------------------------------------
RadianceMapper
--------------------------------------------------------------------------- '''
class RadianceMapper(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("json_input_file"):
            raise ModuleError(self, "Needs json_input")
        json_input_file = self.getInputFromPort("json_input_file")

        #Get the json data
        with open(json_input_file.name) as data_file:    
            data = json.load(data_file)

        #Create the output files
        rad_output_file = self.interpreter.filePool.create_file(suffix=".rad")
        f_rad = open(rad_output_file.name, 'w')
        sensors_output_file = self.interpreter.filePool.create_file(suffix=".txt")
        f_sen = open(sensors_output_file.name, 'w')

        #Create rad surface
        for poly in data["polys"]:
            attribs = poly["attributes"]
            if "rad_type" in attribs:
                if attribs["rad_type"]["value"] == "rad_surface":
                    #check it has the right attribs
                    if "rad_name" in attribs and "rad_material" in attribs:
                        name = attribs["rad_name"]["value"]
                        material = attribs["rad_material"]["value"]
                        points = [data["points"][i] for i in poly["vertices"]]
                        surface_str = surface(name, material, points)
                        f_rad.write(surface_str)
                    else:
                        raise ModuleError(self, "Polygons have missing attributes.")
                elif attribs["rad_type"]["value"] == "rad_sensor":
                    #check it has the right attribs
                    if len(poly["vertices"]) == 2:
                        point_0 = data["points"][poly["vertices"][0]]
                        point_1 = data["points"][poly["vertices"][1]]
                        normal = [point_1[i] - point_0[i] for i in range(3)]
                        sensor_str = sensor(point_0, normal)
                        f_sen.write(sensor_str)
                    else:
                        raise ModuleError(self, "Sensors should have 2 vertices.")

        # Close the files
        f_rad.close()
        f_sen.close()

        #Set output
        self.setResult("rad_output_file", rad_output_file)
        self.setResult("sensors_output_file", sensors_output_file)

    _input_ports = [
        ("json_input_file",FILE,{"optional":False,"sort_key":1})
    ]

    _output_ports = [
        ("rad_output_file",FILE,{"optional":False,"sort_key":2}), 
        ("sensors_output_file",FILE,{"optional":False,"sort_key":1})
    ]

''' ---------------------------------------------------------------------------
RadianceSky
--------------------------------------------------------------------------- '''
class RadianceSky(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("sky_colour"):
            raise ModuleError(self, "Needs sky_colour")
        if not self.hasInputFromPort("ground_colour"):
            raise ModuleError(self, "Needs ground_colour")
        if not self.hasInputFromPort("month"):
            raise ModuleError(self, "Needs month")
        if not self.hasInputFromPort("day"):
            raise ModuleError(self, "Needs day")
        if not self.hasInputFromPort("hour"):
            raise ModuleError(self, "Needs hour")
        if not self.hasInputFromPort("sky_type"):
            raise ModuleError(self, "Needs sky_type")
        if not self.hasInputFromPort("latitude"):
            raise ModuleError(self, "Needs latitude")
        if not self.hasInputFromPort("longitude"):
            raise ModuleError(self, "Needs longitude")
        if not self.hasInputFromPort("meridian"):
            raise ModuleError(self, "Needs meridian")
        sky_colour = self.getInputFromPort("sky_colour")
        ground_colour = self.getInputFromPort("sky_colour")
        month = self.getInputFromPort("month")
        day = self.getInputFromPort("day")
        hour = self.getInputFromPort("hour")
        sky_type = self.getInputFromPort("sky_type")
        latitude = self.getInputFromPort("latitude")
        longitude = self.getInputFromPort("longitude")
        meridian = self.getInputFromPort("meridian")
        
        #Extract colours from object
        sky_colour = sky_colour.tuple
        ground_colour = ground_colour.tuple

        # Create the gensky command
        # http://radsite.lbl.gov/radiance/man_html/gensky.1.html
        gensky = "!gensky " + str(month) + " " + str(day) + " +" + str(hour) + " " + sky_type + " -a " + str(latitude) + " -o " + str(longitude) + " -m " + str(meridian)

        #Create the output file
        sky_output_file = self.interpreter.filePool.create_file(suffix=".rad")
        f = open(sky_output_file.name, 'w')
        sky_glow = glow("sky_glow", sky_colour)
        grd_glow = glow("ground_glow", ground_colour)
        sky_source = source("sky", "sky_glow", (0,0,1))
        grd_source = source("ground", "ground_glow", (0,0,-1))
        sky_data = gensky + "\n\n" + sky_glow + "\n\n" + grd_glow + "\n\n" + sky_source + "\n\n" + grd_source
        f.write(sky_data)
        f.close()

        #Set output
        self.setResult("sky_output_file", sky_output_file)

    _input_ports = [
        ("sky_colour",COLOR,{"optional":False,"sort_key":1}),
        ("ground_colour",COLOR,{"optional":False,"sort_key":2}),
        ("month",INTEGER,{"optional":False,"sort_key":3}),
        ("day",INTEGER,{"optional":False,"sort_key":4}),
        ("hour",INTEGER,{"optional":False,"sort_key":5}),
        ("sky_type",STRING,{"optional":False,"sort_key":6}), #"-s","+s","-c","-i","+i","-u"
        ("latitude",INTEGER,{"optional":False,"sort_key":7}),
        ("longitude",INTEGER,{"optional":False,"sort_key":8}),
        ("meridian",INTEGER,{"optional":False,"sort_key":9})
    ]

    _output_ports = [
        ("sky_output_file",FILE,{"optional":False,"sort_key":1})
    ]

''' ---------------------------------------------------------------------------
RadianceOconv
--------------------------------------------------------------------------- '''

class RadianceOconv(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("base_input_file"):
            raise ModuleError(self, "Needs base_input_file")
        if not self.hasInputFromPort("sky_input_file"):
            raise ModuleError(self, "Needs sky_input_file")
        if not self.hasInputFromPort("rad_input_file"):
            raise ModuleError(self, "Needs rad_input_file")
        base_input_file = self.getInputFromPort("base_input_file")
        sky_input_file = self.getInputFromPort("sky_input_file")
        rad_input_file = self.getInputFromPort("rad_input_file")

        #Get the paths
        base_input_file_path = os.path.abspath(base_input_file.name)
        sky_input_file_path = os.path.abspath(sky_input_file.name)
        rad_input_file_path = os.path.abspath(rad_input_file.name)

        #Create the output file
        oconv_output_file = self.interpreter.filePool.create_file(suffix=".oconv")
        oconv_output_file_path = os.path.abspath(oconv_output_file.name)

        #Execute
        command = "oconv " + base_input_file_path + " " + sky_input_file_path + " " + rad_input_file_path +\
        " > " + oconv_output_file_path
        #command = command.replace("\\","/")
        os.system(command) #EXECUTE!!

        #Set output
        self.setResult("oconv_output_file", oconv_output_file)

    _input_ports = [
        ("base_input_file",FILE,{"optional":False,"sort_key":1}),
        ("sky_input_file",FILE,{"optional":False,"sort_key":2}),
        ("rad_input_file",FILE,{"optional":False,"sort_key":3})

    ]

    _output_ports = [
        ("oconv_output_file",FILE,{"optional":False,"sort_key":1})
    ]

''' ---------------------------------------------------------------------------
RadianceRtrace
--------------------------------------------------------------------------- '''

class RadianceRtrace(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("oconv_input_file"):
            raise ModuleError(self, "Needs oconv_input_file")
        if not self.hasInputFromPort("sensors_input_file"):
            raise ModuleError(self, "Needs sensors_input_file")
        if not self.hasInputFromPort("bounces_ab"):
            raise ModuleError(self, "Needs bounces_ab")
        if not self.hasInputFromPort("accuracy_aa"):
            raise ModuleError(self, "Needs accuracy_aa")
        if not self.hasInputFromPort("resolution_ar"):
            raise ModuleError(self, "Needs resolution_ar")
        if not self.hasInputFromPort("division_ad"):
            raise ModuleError(self, "Needs division_ad")
        if not self.hasInputFromPort("samples_as"):
            raise ModuleError(self, "Needs samples_as")
        oconv_input_file = self.getInputFromPort("oconv_input_file")
        sensors_input_file = self.getInputFromPort("sensors_input_file")
        _ab = str(self.getInputFromPort("bounces_ab"))
        _aa = str(self.getInputFromPort("accuracy_aa"))
        _ar = str(self.getInputFromPort("resolution_ar"))
        _ad = str(self.getInputFromPort("division_ad"))
        _as = str(self.getInputFromPort("samples_as"))

        #Get the paths of the files
        oconv_input_file_path = os.path.abspath(oconv_input_file.name)
        sensors_input_file_path = os.path.abspath(sensors_input_file.name)

        #Create the output file
        results_output_file = self.interpreter.filePool.create_file(suffix=".csv")
        results_output_file_path = os.path.abspath(results_output_file.name)

        #Execute rtrace 
        command = "rtrace -h -w -I+ -ab " +  _ab + " -aa " + _aa + " -ar " + _ar + " -ad " + _ad + " -as " + _as +\
        " " + oconv_input_file_path + " < " + sensors_input_file_path + " > " + results_output_file_path 
        os.system(command) #EXECUTE!!

        #Set output
        self.setResult("results_output_file", results_output_file)

    _input_ports = [
        ("oconv_input_file",FILE,{"optional":False,"sort_key":1}),
        ("sensors_input_file",FILE,{"optional":False,"sort_key":2}),
        ("bounces_ab",INTEGER,{"optional":False,"sort_key":3,"defaults":str(["2"])}),
        ("accuracy_aa",FLOAT,{"optional":False,"sort_key":4,"defaults":str(["0.15"])}),
        ("resolution_ar",INTEGER,{"optional":False,"sort_key":5,"defaults":str(["128"])}),
        ("division_ad",INTEGER,{"optional":False,"sort_key":6,"defaults":str(["512"])}),
        ("samples_as",INTEGER,{"optional":False,"sort_key":7,"defaults":str(["256"])})
    ]

    _output_ports = [
        ("results_output_file",FILE,{"optional":False,"sort_key":1})
    ]


''' ---------------------------------------------------------------------------
RadianceReadings - converts raw data to Irradiance and Illuminance
--------------------------------------------------------------------------- '''

class RadianceReadings(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("results_input_file"):
            raise ModuleError(self, "Needs results_input_file")
        results_input_file = self.getInputFromPort("results_input_file")

        #Get the paths of the files
        results_input_file_path = os.path.abspath(results_input_file.name)

        #Create the output file
        irradiance_output_file = self.interpreter.filePool.create_file(suffix=".csv")
        irradiance_output_file_path = os.path.abspath(irradiance_output_file.name)
        illuminance_output_file = self.interpreter.filePool.create_file(suffix=".csv")
        illuminance_output_file_path = os.path.abspath(illuminance_output_file.name)

        #Open the results file
        f_in = open(results_input_file_path)
        f_irradiance = open(irradiance_output_file_path, 'w')
        f_illuminance = open(illuminance_output_file_path, 'w')
        for line in f_in:
            words  = line.split()
            numbers = map(float, words)
            #IRRADIANCE RESULTS 
            irradiance = round((0.265 * numbers[0]) + (0.670 * numbers[1]) + (0.065 * numbers[2]), 1)
            f_irradiance.write(str(irradiance) + "\n")
            #ILLUMINANCE RESULTS            
            illuminance = irradiance * 179
            f_illuminance.write(str(illuminance) + "\n")

        #Close the files
        f_in.close()
        f_irradiance.close()
        f_illuminance.close()

        #Set output
        self.setResult("irradiance_output_file", irradiance_output_file)
        self.setResult("illuminance_output_file", illuminance_output_file)

    _input_ports = [
        ("results_input_file",FILE,{"optional":False,"sort_key":1})
    ]

    _output_ports = [
        ("irradiance_output_file",FILE,{"optional":False,"sort_key":2}),
        ("illuminance_output_file",FILE,{"optional":False,"sort_key":1})
    ]

''' ---------------------------------------------------------------------------
RadianceResults - converts raw data to Irradiance and Illuminance
--------------------------------------------------------------------------- '''

class RadianceResults(Module):
    def compute(self):

        #Check inputs
        if not self.hasInputFromPort("results_input_file"):
            raise ModuleError(self, "Needs results_input_file")
        results_input_file = self.getInputFromPort("results_input_file")

        #Get the paths of the files
        results_input_file_path = os.path.abspath(results_input_file.name)

        #Open the results file
        f_in = open(results_input_file_path)
        irradiance_list = []
        illuminance_list = []
        for line in f_in:
            words  = line.split()
            numbers = map(float, words)
            #IRRADIANCE RESULTS 
            irradiance = round((0.265 * numbers[0]) + (0.670 * numbers[1]) + (0.065 * numbers[2]), 1)
            irradiance_list.append(irradiance)
            #ILLUMINANCE RESULTS            
            illuminance = irradiance * 179
            illuminance_list.append(illuminance)

        #Close the files
        f_in.close()

        #Set output
        self.setResult("irradiance", irradiance_list)
        self.setResult("illuminance", illuminance_list)

    _input_ports = [
        ("results_input_file",FILE,{"optional":False,"sort_key":1})
    ]

    _output_ports = [
        ("irradiance",LIST,{"optional":False,"sort_key":2}),
        ("illuminance",LIST,{"optional":False,"sort_key":1})
    ]